#include "glad/glad.h"
#include "GLFW/glfw3.h"

#include "Common.h"
#include "ShaderProgram.h"

GLuint VAO;
ShaderProgram Program;

void Init();
void ProcessEvents(GLFWwindow* window);
void Draw();

int main()
{
    if(!glfwInit())
    {
        ERROR("glfwInit failed")
        return -1;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(ScreenWidth, ScreenHeight, "Demo", nullptr, nullptr);
    if(!window)
    {
        glfwTerminate();
        ERROR("glfwCreateWindow failed")
        return -1;
    }
    glfwMakeContextCurrent(window);

    if(!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress))
    {
        glfwDestroyWindow(window);
        glfwTerminate();
        ERROR("gladLoadGLLoader failed")
        return -1;
    }

    Init();
    while(!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        ProcessEvents(window);
        Draw();
        glfwSwapBuffers(window);
    }

    glDeleteVertexArrays(1, &VAO);

    glfwDestroyWindow(window);
    glfwTerminate();

    return 0;
}

void Init()
{
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    ShaderInfo info("../../res/vert.glsl",
                    "../../res/frag.glsl");
    Program = ShaderProgram(info);

//    // This is the data that we want to place into the buffer object
//    static const float data[] = {
//            -0.5f, -0.5f, 0.0f,
//            0.5f, -0.5f, 0.0f,
//            0.0f, 0.5f, 0.0f
//    };
//
//    // create and initializing a buffer
//    GLuint buffer;
//    glGenBuffers(1, &buffer);
//    glBindBuffer(GL_ARRAY_BUFFER, buffer);
//    // version >= 4.5：use glBufferStorage/glNamedBufferStorage to allocate memory
//    // first command is used for specified target,
//    // second command is used for specified named buffer directly
//    glBufferData(GL_ARRAY_BUFFER, 1024 * 1024, nullptr, GL_STATIC_DRAW);
//
//    // there's two way to put the above data into the buffer
//    // one is tell opengl that go there to copy data and put it into the buffer at offset 0
//    // use glBufferSubData/glNamedBufferSubData
//    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof data, data);
//    // another way to put data is use glMapBuffer/glMapNamedBuffer and you can maintain completely
//    // 1. get the ptr to the buffer's data store
//    void* ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
//    // 2. copy data into it
//    memcpy(ptr, data, sizeof(data));
//    // 3. tell opengl that we're done with the ptr
//    glUnmapBuffer(GL_ARRAY_BUFFER);
//
//    // create vertex array object
//    glGenVertexArrays(1, &VAO);
//    glBindVertexArray(VAO);
//    // each vertex attribute gets to fetch data from a buffer bound to one of
//    // several vertex buffer bindings. to set the binding that a vertex attribute
//    // uses to reference a buffer, call the glVertexAttribBinding/glVertexArrayAttribBinding
//    // first is effect the vao bound in target
//    // last is function requires version>=4.5 and it can specifies a vao
//    glVertexAttribBinding(0, 0);
//    // next. use glBindVertexBuffer/glVertexArrayVertexBuffer to give a binding between vao and vbo
//    glBindVertexBuffer(0, buffer, 0, 3 * sizeof(GLfloat));
//    // next glVertexAttribFormat/glVertexArrayAttribFormat to configure
//    glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0);
//    // next glEnableVertexAttribArray
//    glEnableVertexAttribArray(0);

    struct Vertex
    {
        float x;
        float y;
        float z;

        float r;
        float g;
        float b;

        Vertex(float x, float y, float z, float r, float g, float b)
            : x(x), y(y), z(z), r(r), g(g), b(b)
        {

        }
    };

    static Vertex vertices[] = {
            Vertex(-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f),
            Vertex(0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f),
            Vertex(0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f)
    };

    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);
    void* ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    memcpy(ptr, vertices, sizeof vertices);
    glUnmapBuffer(GL_ARRAY_BUFFER);

    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0);
    glBindVertexBuffer(0, buffer, 0, 6 * sizeof(GLfloat));
    glVertexAttribFormat(1, 3, GL_FLOAT, GL_FALSE, 0);
    glBindVertexBuffer(1, buffer, 3 * sizeof(GLfloat), 6 * sizeof(GLfloat));
    glVertexAttribBinding(0, 0);
    glVertexAttribBinding(1, 1);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
}

void ProcessEvents(GLFWwindow* window)
{

}

void Draw()
{
    static const GLfloat bgColor[] = {0.2f, 0.2f, 0.2f, 1.0f};
    glClearBufferfv(GL_COLOR, 0, bgColor);

    Program.Use();

//    auto curTime = static_cast<float>(glfwGetTime());
//    GLfloat offsets[] = {
//            sinf(curTime) * 0.5f,
//            cosf(curTime) * 0.5f,
//            0.0f,
//            0.0f
//    };
//    GLfloat color[] = {
//            sinf(curTime) * 0.5f,
//            cosf(curTime) * 0.5f,
//            0.0f,
//            1.0f
//    };
//    glVertexAttrib4fv(0, offsets);
//    glVertexAttrib4fv(1, color);

    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}